package com.panfeng.xcloud.scheduler.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.panfeng.xcloud.boss.provider.dto.request.OffSetDetailDTO;
import com.panfeng.xcloud.boss.provider.dto.request.OperateBotReqDTO;
import com.panfeng.xcloud.boss.provider.dto.request.QuantBotDetailDTO;
import com.panfeng.xcloud.common.core.constants.RedisConstants;
import com.panfeng.xcloud.common.core.enums.OperateTypeEnum;
import com.panfeng.xcloud.common.core.enums.PositionSideTypeEnum;
import com.panfeng.xcloud.common.core.enums.ResponseStatusEnum;
import com.panfeng.xcloud.common.core.enums.SystemInnerTypeEnum;
import com.panfeng.xcloud.common.core.exceptions.BaseBizException;
import com.panfeng.xcloud.common.core.utils.CollectionUtils;
import com.panfeng.xcloud.common.core.utils.ConvertUtils;
import com.panfeng.xcloud.common.core.utils.KeyContactUtil;
import com.panfeng.xcloud.common.core.utils.StringUtils;
import com.panfeng.xcloud.common.core.web.vo.OrderDetailResponseVO;
import com.panfeng.xcloud.common.core.web.vo.TradeResponseVO;
import com.panfeng.xcloud.scheduler.service.IFeignService;
import com.panfeng.xcloud.scheduler.service.IOffSetService;
import groovy.util.logging.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Slf4j
@Service
public class OffSetServiceImpl implements IOffSetService {

    private static Logger log = LoggerFactory.getLogger(OffSetServiceImpl.class);

    @Autowired
    private IFeignService feignService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void offSet() {
        Set<String> keys = stringRedisTemplate.keys(RedisConstants.OFF_SET_DETAIL);
        //遍历对冲仓key
        for (String key : keys) {
            try {
                String result = stringRedisTemplate.opsForValue().get(key);
                if (StringUtils.isEmpty(result)) {
                    continue;
                }

                ArrayList<OffSetDetailDTO> needRemoveDTOs = new ArrayList<>();
                List<OffSetDetailDTO> offSets = JSONArray.parseArray(result, OffSetDetailDTO.class);
                for (OffSetDetailDTO offSet : offSets) {
                    try {
                        //判断该笔对冲仓该执行什么操作(止盈,止损,无操作)
                        Boolean operate = toExecuteOffSet(offSet);

                        //如果执行了止盈或者止损,则将该对冲仓加入需要删除的集合中;如果无操作,则无需操作
                        if (operate) {
                            needRemoveDTOs.add(offSet);
                        }
                    } catch (Exception e) {
                        feignService.sendEmail("操作对冲仓失败,"
                                + "  key:" + key
                                + "  失败原因: " + StringUtils.printStackTraceToString(e));
                    }
                }

                //将需要删除的对冲仓从redis中清除
                offSets.removeAll(needRemoveDTOs);

                if (CollectionUtils.isEmpty(offSets)) {
                    //删除redis中的对冲仓key
                    stringRedisTemplate.delete(key);
                } else {
                    //更新redis中的对冲仓列表
                    stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(offSets));
                }

            } catch (Exception e) {
                feignService.sendEmail("查询对冲仓key失败,"
                        + "  key: " + key
                        + "  失败原因: " + StringUtils.printStackTraceToString(e));
            }
        }
    }

    /**
     * 加对冲,发送相应邮件
     */
    @Override
    @Async("asyncExecutor")
    public void doOffSetAndEmail(QuantBotDetailDTO quantBotDetail, BigDecimal amount, SystemInnerTypeEnum systemInnerTypeEnum, String stopProfitRatio, String stopLossRatio) {
        QuantBotDetailDTO offSetQuantBotDetailDTO = ConvertUtils.convert(quantBotDetail, QuantBotDetailDTO.class);
        try {
            //因为是对冲仓,所以实际上是应该下反仓位
            offSetQuantBotDetailDTO.setDirection(quantBotDetail.getDirection().equals(PositionSideTypeEnum.BUY.getCode()) ? PositionSideTypeEnum.SELL.getCode() : PositionSideTypeEnum.BUY.getCode());

            //加对冲单
            OrderDetailResponseVO orderDetailResponseVO = doOffSetOrder(offSetQuantBotDetailDTO, amount, systemInnerTypeEnum.code);

            String offSetKey = KeyContactUtil.getOffSetKey(Integer.valueOf(offSetQuantBotDetailDTO.getExchangeType()), offSetQuantBotDetailDTO.getDirection(),
                    Integer.valueOf(offSetQuantBotDetailDTO.getLeverRate()), offSetQuantBotDetailDTO.getDestinationCoin(), offSetQuantBotDetailDTO.getSourceCoin(), offSetQuantBotDetailDTO.getUserId());

            //获取对冲仓列表
            String result = stringRedisTemplate.opsForValue().get(offSetKey);
            List<OffSetDetailDTO> offSetList;
            if (StringUtils.isEmpty(result)) {
                offSetList = new ArrayList<>();
            } else {
                offSetList = JSONArray.parseArray(result, OffSetDetailDTO.class);
            }

            //将本次对冲仓加入对冲仓列表
            OffSetDetailDTO offSetDetailDTO = new OffSetDetailDTO();
            offSetDetailDTO.setUserId(offSetQuantBotDetailDTO.getUserId());
            offSetDetailDTO.setOffSetDestinationCoin(offSetQuantBotDetailDTO.getDestinationCoin());
            offSetDetailDTO.setOffSetSourceCoin(offSetQuantBotDetailDTO.getSourceCoin());
            offSetDetailDTO.setOffSetExchangeType(offSetQuantBotDetailDTO.getExchangeType());
            offSetDetailDTO.setOffSetContractCode(offSetQuantBotDetailDTO.getContractCode());
            offSetDetailDTO.setOffSetPositionAmount(String.valueOf(orderDetailResponseVO.getTradeTurnover()));
            offSetDetailDTO.setOffSetPositionNum(String.valueOf(orderDetailResponseVO.getTradeVolumeNum()));
            offSetDetailDTO.setOffSetPositionPiece(String.valueOf(orderDetailResponseVO.getTradeVolume()));
            offSetDetailDTO.setOffSetAvgPrice(String.valueOf(orderDetailResponseVO.getTradeAvgPrice()));
            offSetDetailDTO.setDirection(offSetQuantBotDetailDTO.getDirection());
            offSetDetailDTO.setLeverRate(offSetQuantBotDetailDTO.getLeverRate());
            offSetDetailDTO.setStopProfitRate(stopProfitRatio);
            offSetDetailDTO.setStopLossRate(stopLossRatio);
            offSetList.add(offSetDetailDTO);

            //更新redis中的对冲仓列表
            stringRedisTemplate.opsForValue().set(offSetKey, JSON.toJSONString(offSetList));

            //发出提醒邮件
            feignService.sendEmail(systemInnerTypeEnum.getInfo()
                    + "  对冲仓方向: " + offSetQuantBotDetailDTO.getDirection()
                    + "  UserId:" + offSetQuantBotDetailDTO.getUserId()
                    + "  对冲数量:" + orderDetailResponseVO.getTradeVolumeNum()
                    + "  交易对:" + offSetQuantBotDetailDTO.getContractCode()
                    + "  倍数:" + offSetQuantBotDetailDTO.getLeverRate());
        } catch (Exception e) {
            feignService.sendEmail(systemInnerTypeEnum.getInfo()
                    + "  对冲仓方向: " + offSetQuantBotDetailDTO.getDirection()
                    + "  UserId:" + offSetQuantBotDetailDTO.getUserId()
                    + "  交易对:" + offSetQuantBotDetailDTO.getContractCode()
                    + "  倍数:" + offSetQuantBotDetailDTO.getLeverRate()
                    + "  失败原因: " + StringUtils.printStackTraceToString(e));
        }
    }

    private Boolean toExecuteOffSet(OffSetDetailDTO offSet) throws Exception {
        //获取该交易对当前买盘价格
        BigDecimal currentBidPrice = getCurrentBidPrice(Integer.valueOf(offSet.getOffSetExchangeType()), offSet.getUserId(), offSet.getOffSetDestinationCoin(), offSet.getOffSetSourceCoin());

        //当前均价
        BigDecimal avgPrice = new BigDecimal(offSet.getOffSetAvgPrice());

        //获取当前对冲仓涨跌幅,用来计算止盈
        BigDecimal riseRate = null;

        //获取当前对冲仓浮动盈亏,用来计算止损
        //BigDecimal floatProfitAndLoss = null;

        if (offSet.getDirection().equals(PositionSideTypeEnum.BUY.getCode())) {
            //如果对冲仓是多单,那么上涨是止盈,下跌是止损
            riseRate = (currentBidPrice.subtract(avgPrice)).divide(avgPrice, 6, BigDecimal.ROUND_DOWN);
        } else if (offSet.getDirection().equals(PositionSideTypeEnum.SELL.getCode())) {
            //如果对冲仓是空单,那么上涨是止损,下跌是止盈
            riseRate = (avgPrice.subtract(currentBidPrice)).divide(avgPrice, 6, BigDecimal.ROUND_DOWN);
        } else {
            throw new BaseBizException(ResponseStatusEnum.ROBOT_DIRECTION_NOT_EXIST);
        }

        //floatProfitAndLoss = new BigDecimal(offSet.getOffSetPositionAmount()).multiply(riseRate);

        if (riseRate.compareTo(new BigDecimal(offSet.getStopProfitRate())) >= 0) {
            //对冲仓止盈
            OperateBotReqDTO operateBotReqDTO = initOffSetReqDTO(OperateTypeEnum.SELL.getCode(), offSet);
            TradeResponseVO tradeResponseVO = feignService.chooseSendOrderHttp(operateBotReqDTO, SystemInnerTypeEnum.OFF_SET_STOP_PROFIT.code);
            OrderDetailResponseVO orderDetail = feignService.getOrderDetail(Integer.valueOf(operateBotReqDTO.getExchangeType()), operateBotReqDTO, tradeResponseVO);
            log.info("执行了对冲仓止盈");
            //发出提醒邮件
            feignService.sendEmail("执行了对冲仓止盈,"
                    + "  UserId:" + offSet.getUserId()
                    + "  对冲仓方向: " + offSet.getDirection()
                    + "  对冲数量:" + offSet.getOffSetPositionPiece()
                    + "  交易对:" + offSet.getOffSetContractCode()
                    + "  倍数:" + offSet.getLeverRate()
                    + "  交易所:" + offSet.getOffSetExchangeType()
                    + "  盈亏数量:" + orderDetail.getRealProfit()
            );
            return true;
        } else if (riseRate.compareTo(new BigDecimal(offSet.getStopLossRate())) <= 0) {
            //对冲仓止损
            OperateBotReqDTO operateBotReqDTO = initOffSetReqDTO(OperateTypeEnum.SELL.getCode(), offSet);
            TradeResponseVO tradeResponseVO = feignService.chooseSendOrderHttp(operateBotReqDTO, SystemInnerTypeEnum.OFF_SET_STOP_LOSS.code);
            OrderDetailResponseVO orderDetail = feignService.getOrderDetail(Integer.valueOf(operateBotReqDTO.getExchangeType()), operateBotReqDTO, tradeResponseVO);
            log.info("执行了对冲仓止损");
            //发出提醒邮件
            feignService.sendEmail("执行了对冲仓止损,"
                    + "  UserId:" + offSet.getUserId()
                    + "  对冲仓方向: " + offSet.getDirection()
                    + "  对冲数量:" + offSet.getOffSetPositionPiece()
                    + "  交易对:" + offSet.getOffSetContractCode()
                    + "  倍数:" + offSet.getLeverRate()
                    + "  交易所:" + offSet.getOffSetExchangeType()
                    + "  盈亏数量:" + orderDetail.getRealProfit()
            );
            return true;
        }

        return false;
    }

    private BigDecimal getCurrentBidPrice(Integer exchangeType, String userId, String destinationCoin, String sourceCoin) throws Exception {
        JSONObject depth = feignService.getMarketPriceDepth(exchangeType, userId, destinationCoin, sourceCoin);
        JSONArray bids = depth.getJSONArray("bids");
        JSONArray bid = (JSONArray) (bids.get(0));
        BigDecimal bidPrice = new BigDecimal(String.valueOf(bid.get(0)));
        log.info("当前买价格:{}", bidPrice);
        return bidPrice;
    }

    private OrderDetailResponseVO doOffSetOrder(QuantBotDetailDTO quantBotDetailDTO, BigDecimal amount, Integer systemInnerType) throws Exception {
        OperateBotReqDTO operateBotReqDTO = initOperateBotReqDTO(OperateTypeEnum.BUY.getCode(), quantBotDetailDTO);
        operateBotReqDTO.setAmount(amount);
        TradeResponseVO tradeResponseVO = feignService.chooseSendOrderHttp(operateBotReqDTO, systemInnerType);
        OrderDetailResponseVO orderDetail = feignService.getOrderDetail(Integer.valueOf(operateBotReqDTO.getExchangeType()), operateBotReqDTO, tradeResponseVO);
        return orderDetail;
    }

    private OperateBotReqDTO initOffSetReqDTO(int operateType, OffSetDetailDTO offSet) {
        OperateBotReqDTO operateBotReqDTO = new OperateBotReqDTO();
        operateBotReqDTO.setUserId(offSet.getUserId());
        operateBotReqDTO.setOperateType(operateType);
        operateBotReqDTO.setSourceCoin(offSet.getOffSetSourceCoin());
        operateBotReqDTO.setDestinationCoin(offSet.getOffSetDestinationCoin());
        operateBotReqDTO.setExchangeType(Integer.valueOf(offSet.getOffSetExchangeType()));
        operateBotReqDTO.setAmount(new BigDecimal(offSet.getOffSetPositionPiece()));
        operateBotReqDTO.setDirection(offSet.getDirection());
        operateBotReqDTO.setLeverRate(Integer.valueOf(offSet.getLeverRate()));
        return operateBotReqDTO;
    }

    private OperateBotReqDTO initOperateBotReqDTO(int operateType, QuantBotDetailDTO quantBotDetailDTO) {
        OperateBotReqDTO operateBotReqDTO = new OperateBotReqDTO();
        operateBotReqDTO.setUserId(quantBotDetailDTO.getUserId());
        operateBotReqDTO.setOperateType(operateType);
        operateBotReqDTO.setSourceCoin(quantBotDetailDTO.getSourceCoin());
        operateBotReqDTO.setDestinationCoin(quantBotDetailDTO.getDestinationCoin());
        operateBotReqDTO.setExchangeType(Integer.valueOf(quantBotDetailDTO.getExchangeType()));
        operateBotReqDTO.setAmount(new BigDecimal(quantBotDetailDTO.getPositionPiece()));
        operateBotReqDTO.setDirection(quantBotDetailDTO.getDirection());
        operateBotReqDTO.setLeverRate(Integer.valueOf(quantBotDetailDTO.getLeverRate()));
        return operateBotReqDTO;
    }

}
